/*
# pymoz, Python extension for the Mozilla Toolkit
# Copyright (C) 2012 Malek Hadj-Ali
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include "pymoz.h"
#include "pyRuntime.h"

#include "mozilla/ModuleUtils.h"

#include "prlink.h"


#define NS_AGENT_STYLE_SHEETS "agent-style-sheets"
#define PYMOZ_BINDINGS_ENTRY "Python extension bindings"
#define PYMOZ_BINDINGS_URI "chrome://pymoz/content/pymoz.css"


static PRLibrary *gPythonLib = NULL;


/*******************************************************************************
* global utils
*******************************************************************************/

PyObject *
Py_GetGlobals()
{
    PyObject *globals = PyEval_GetGlobals(); // borrowed
    if (!globals) {
        PyObject *pymain = PyImport_AddModule("__main__"); // borrowed
        if (pymain) {
            globals = PyModule_GetDict(pymain); // borrowed
        }
    }
    return globals;
}


PyObject *
PyModule_GetAttrId(const char *modname, _Py_Identifier *attrid)
{
    PyObject *module = PyImport_ImportModuleNoBlock(modname);
    if (!module) {
        return NULL;
    }
    PyObject *result = _PyObject_GetAttrId(module, attrid);
    Py_DECREF(module);
    return result;
}


int
PyModule_SetAttrId(const char *modname, _Py_Identifier *attrid, PyObject *value)
{
    PyObject *module = PyImport_ImportModuleNoBlock(modname);
    if (!module) {
        return -1;
    }
    int result = _PyObject_SetAttrId(module, attrid, value);
    Py_DECREF(module);
    return result;
}


/*******************************************************************************
 utils
*******************************************************************************/

static int
PyModule_AddException(PyObject *pymodule, const char *name, PyObject *pyexc)
{
    Py_INCREF(pyexc);
    if (PyModule_AddObject(pymodule, name, pyexc)) {
        Py_DECREF(pyexc);
        return -1;
    }
    return 0;
}


static PyObject *
PyWarn_Display(PyObject *message, PyObject *category, PyObject *filename,
               PyObject *lineno, PyObject *file, PyObject *line)
{
    _Py_IDENTIFIER(__showwarning__);

    PyObject *__showwarning__ = PyModule_GetAttrId("warnings",
                                                   &PyId___showwarning__);
    if (!__showwarning__) {
        return NULL;
    }
    return PyObject_CallFunctionObjArgs(__showwarning__,
                                        message, category, filename, lineno,
                                        file, line, NULL);
}


/*******************************************************************************
 pymoz_module
*******************************************************************************/

/* pymoz_module.m_doc */
PyDoc_STRVAR(pymoz_m_doc,
"pymoz module");


/* pymoz.excepthook(type, value, traceback) */
PyDoc_STRVAR(pymoz_excepthook_doc,
"excepthook(type, value, traceback)");

static PyObject *
pymoz_excepthook(PyObject *module, PyObject *args)
{
    PyObject *exctype, *excvalue, *exctraceback;

    if (!PyArg_UnpackTuple(args, "excepthook", 3, 3,
                           &exctype, &excvalue, &exctraceback)) {
        return NULL;
    }
    PyErr_Display(exctype, excvalue, exctraceback);
    if (!pyRuntime::SetException(excvalue)) {
        return NULL;
    }
    Py_RETURN_NONE;
}


/* pymoz.showwarning(message, category, filename, lineno, file=None, line=None) */
PyDoc_STRVAR(pymoz_showwarning_doc,
"showwarning(message, category, filename, lineno, file=None, line=None)");

static PyObject *
pymoz_showwarning(PyObject *module, PyObject *args)
{
    PyObject *message, *category, *filename, *lineno;
    PyObject *file = Py_None, *line = Py_None;

    if (!PyArg_UnpackTuple(args, "showwarning", 4, 6,
                           &message, &category, &filename, &lineno,
                           &file, &line)) {
        return NULL;
    }
    PyObject *tmp = PyWarn_Display(message, category, filename, lineno,
                                   file, line);
    if (!tmp) {
        return NULL;
    }
    Py_DECREF(tmp);
    if (!pyRuntime::ReportWarning(message, category, filename, lineno, line)) {
        return NULL;
    }
    Py_RETURN_NONE;
}


/* pymoz.window() -> current window */
PyDoc_STRVAR(pymoz_window_doc,
"window() -> current window object");

static PyObject *
pymoz_window(PyObject *module)
{
    return pyRuntime::GetWindow();
}


/* pymoz_module.m_methods */
static PyMethodDef pymoz_m_methods[] = {
    {"excepthook", (PyCFunction)pymoz_excepthook, METH_VARARGS,
     pymoz_excepthook_doc},
    {"showwarning", (PyCFunction)pymoz_showwarning, METH_VARARGS,
     pymoz_showwarning_doc},
    {"window", (PyCFunction)pymoz_window, METH_NOARGS,
     pymoz_window_doc},
    {NULL} /* Sentinel */
};


/* pymoz_module */
static PyModuleDef pymoz_module = {
    PyModuleDef_HEAD_INIT,
    "pymoz",                                  /*m_name*/
    pymoz_m_doc,                              /*m_doc*/
    -1,                                       /*m_size*/
    pymoz_m_methods,                          /*m_methods*/
};


/* pymoz_module initialization */
PyMODINIT_FUNC
PyInit_pymoz(void)
{
    PyObject *pymoz = NULL;

    /* pymoz */
    pymoz = PyModule_Create(&pymoz_module);
    if (!pymoz) {
        return NULL;
    }
    /* exceptions */
    if (
        PyModule_AddException(pymoz, "Error", pyRuntime::sError) ||
        PyModule_AddException(pymoz, "JSError", pyRuntime::sJSError) ||
        PyModule_AddException(pymoz, "XPCOMError", pyRuntime::sXPCOMError) ||
        PyModule_AddException(pymoz, "JSWarning", pyRuntime::sJSWarning)
       ) {
        goto fail;
    }
    return pymoz;

fail:
    Py_DECREF(pymoz);
    return NULL;
}


/*******************************************************************************
* mozilla Module implementation
*******************************************************************************/

NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(pyRuntime, pyRuntime::GetService)
NS_DEFINE_NAMED_CID(PY_RUNTIME_CID);


static const mozilla::Module::CIDEntry kPythonCIDs[] = {
    { &kPY_RUNTIME_CID, true, NULL, pyRuntimeConstructor },
    { NULL }
};


static const mozilla::Module::ContractIDEntry kPythonContractIDs[] = {
    { PY_RUNTIME_CONTRACTID, &kPY_RUNTIME_CID },
    { NULL }
};


static const mozilla::Module::CategoryEntry kPythonCategories[] = {
    { NS_AGENT_STYLE_SHEETS, PYMOZ_BINDINGS_ENTRY, PYMOZ_BINDINGS_URI },
    { NULL }
};


/* Load/Unload */

static nsresult
LoadPython()
{
    char *libname = PR_GetLibraryName(NULL, PYTHON_LIBNAME);
    if (!libname) {
        return NS_ERROR_OUT_OF_MEMORY;
    }
    PRLibSpec libspec;
    libspec.type = PR_LibSpec_Pathname;
    libspec.value.pathname = libname;
    gPythonLib = PR_LoadLibraryWithFlags(libspec, PR_LD_LAZY | PR_LD_GLOBAL);
    PR_FreeLibraryName(libname);
    if (!gPythonLib) {
        return NS_ERROR_FAILURE;
    }
    if (PyImport_AppendInittab("pymoz", &PyInit_pymoz)) {
        return NS_ERROR_FAILURE;
    }
    Py_Initialize();
    if (!Py_IsInitialized()) {
        return NS_ERROR_FAILURE;
    }
    return NS_OK;
}


static void
UnloadPython()
{
    if (gPythonLib) {
        if (Py_IsInitialized()) {
            Py_Finalize();
        }
        PR_UnloadLibrary(gPythonLib);
        gPythonLib = NULL;
    }
}


/* PythonModule */
static const mozilla::Module kPythonModule = {
    mozilla::Module::kVersion,
    kPythonCIDs,
    kPythonContractIDs,
    kPythonCategories,
    NULL,
    LoadPython,
    UnloadPython
};


NSMODULE_DEFN(PythonModule) = &kPythonModule;
